5163

26 分钟

#C 语言的结构体 struct

C 语言中的结构体是一种自定义的数据类型,它允许我们将不同类型的数据组合成一个整体。结构体是 C 语言中实现复杂数据结构的基础,也是面向对象编程中"类"概念的雏形。

结构体的定义使用 struct 关键字:

// 定义结构体类型 struct 结构体类型名 { 类型 变量名1; 类型 变量名2; 类型 变量名3; // ... }; // 定义结构体类型的变量 struct 结构体类型名 变量名;

例如,定义一个表示玩家信息的结构体:

// 定义结构体类型 struct Player struct Player { char name[64]; // 名称 int hp; // 生命值 int mp; // 魔法值 }; // 入口函数 int main(void) { // 定义 struct Player 类型的变量 p1 p2 struct Player p1 = {'Mario', 6, 10}; struct Player p2 = {'Luigi', 5, 15}; }

这里定义了一个名为 Player 的结构体类型,它包含名称(name),生命值(hp)和魔法值(mp)三个成员。

然后定义了 struct Player 类型的变量 p1p2

  • 前者的名称为 Mario,拥有 6 点生命值和 10 点魔法值
  • 后者名为 Luigi,拥有 5 点生命值和 15 点魔法值

#访问结构体成员

通过结构体类型的变量访问其内部成员时需要使用 . 运算符,例如:

#include <stdio.h> // 定义结构体类型 struct Player struct Player { char name[64]; // 名称 int hp; // 生命值 int mp; // 魔法值 }; int main(void) { struct Player p1 = {'Mario', 6, 10}; printf("name: %s\n", p1.name); // 通过 . 访问成员 name printf("hp: %d\n", p1.hp); // 通过 . 访问成员 hp printf("mp: %d\n", p1.mp); // 通过 . 访问成员 mp return 0; }

通过结构体指针类型的变量访问其内部成员时需要使用 -> 运算符,例如:

#include <stdio.h> // 定义结构体类型 struct Player struct Player { char name[64]; // 名称 int hp; // 生命值 int mp; // 魔法值 }; int main(void) { struct Player p1 = {'Mario', 6, 10}; struct Player* ptr = &p1; // 指针指向 p1 printf("name: %s\n", ptr->name); // 通过 -> 访问成员 name printf("hp: %d\n", ptr->hp); // 通过 -> 访问成员 hp printf("mp: %d\n", ptr->mp); // 通过 -> 访问成员 mp return 0; }

#结构体大小与内存对齐

显然,结构体的大小就是其成员大小之和。例如:

#include <stdio.h> // 定义结构体类型 struct Player struct Player { char name[64]; // 64 字节 int hp; // 4 字节 int mp; // 4 字节 }; int main(void) { printf("struct Player 的大小是 %zu 字节\n", sizeof(struct Player)); return 0; }

运行结果:

struct Player 的大小是 72 字节

这里 struct Player 的大小为 字节

但是有时候简单的相加结果却不正确。例如:

#include <stdio.h> // 定义结构体类型 struct Player struct Player { char name[65]; // 65 字节 int hp; // 4 字节 int mp; // 4 字节 }; int main(void) { printf("struct Player 的大小是 %zu 字节\n", sizeof(struct Player)); return 0; }

运行结果:

struct Player 的大小是 76 字节

这里将数组 name 从 64 个 char 改为了 65 个 char,而 struct Player 并非 字节,而是 76 字节。

这是因为这里发生了内存对齐,导致 namehp 之间空了 3 个字节。

内存对齐(Memory Alignment) 是指数据在内存中的存储位置(地址)必须是某个数值的整数倍。

这样做的目的是提高硬件访问数据的性能。现代计算机硬件中的CPU在数据 自然对齐 的情况下,能够最高效地执行内存读写操作。

所谓 自然对齐 就是 N 字节的数据类型按照 N 字节对齐,即内存地址是 N 的整数倍

上述结构体中的 hp 是 4 个字节,自然对齐即 4 字节对齐,也就是内存地址是 4 的整数倍。

假设 struct Player 的内存地址从 0 开始,那么如果不对齐的话,hp 的内存地址应当是 65。

而 4 字节对齐时,65 不是 4 的正数倍,大于 65 的 4 的最小整数倍数是 68,因此 hp 会空 3 个字节放置到地址 68 的内存位置。

#include <stdio.h> // 定义结构体类型 struct Player struct Player { char name[65]; // 65 字节 int hp; // 4 字节 int mp; // 4 字节 }; int main(void) { struct Player p; printf("struct Player 的大小是 %zu 字节\n", sizeof(p)); printf("p 的地址是 %p\n", &p); printf("p.name 的地址是 %p,偏移量为 %td\n", &p.name, (char*)&p.name - (char*)&p); printf("p.hp 的地址是 %p,偏移量为 %td\n", &p.hp, (char*)&p.hp - (char*)&p); printf("p.mp 的地址是 %p,偏移量为 %td\n", &p.mp, (char*)&p.mp - (char*)&p); return 0; }

运行结果:

struct Player 的大小是 76 字节 p 的地址是 0x7ffd69a20b90 p.name 的地址是 0x7ffd69a20b90,偏移量为 0 p.hp 的地址是 0x7ffd69a20bd4,偏移量为 68 p.mp 的地址是 0x7ffd69a20bd8,偏移量为 72

#修改对齐方式

可以使用预处理指令 #pragma pack 可以修改对齐方式。

#pragma pack(n) // 设为 n(1, 2, 4, 8, 16...)字节对齐 #pragma pack() // 设为默认的对齐方式,通常是自然对齐 #pragma pack(push) // 保存当前的对齐方式 #pragma pack(pop) // 恢复之前保存的对齐方式

例如:

#include <stdio.h> #pragma pack(push) // 保存当前的对齐方式 #pragma pack(1) // 设为 1 字节对齐,也就是不对齐 // 定义结构体类型 struct Player struct Player { char name[65]; // 65 字节 int hp; // 4 字节 int mp; // 4 字节 }; #pragma pack(pop) // 恢复之前保存的对齐方式 int main(void) { struct Player p; printf("struct Player 的大小是 %zu 字节\n", sizeof(p)); printf("p 的地址是 %p\n", &p); printf("p.name 的地址是 %p,偏移量为 %td\n", &p.name, (char*)&p.name - (char*)&p); printf("p.hp 的地址是 %p,偏移量为 %td\n", &p.hp, (char*)&p.hp - (char*)&p); printf("p.mp 的地址是 %p,偏移量为 %td\n", &p.mp, (char*)&p.mp - (char*)&p); return 0; }

运行结果:

struct Player 的大小是 73 字节 p 的地址是 0x7ffe1e9ef8c0 p.name 的地址是 0x7ffe1e9ef8c0,偏移量为 0 p.hp 的地址是 0x7ffe1e9ef901,偏移量为 65 p.mp 的地址是 0x7ffe1e9ef905,偏移量为 69

#结构体的位域

定义结构体类型时,可以在其成员后面通过添加 : bit 的方式注明该字段的位数。

这个功能常用在硬件操作、格式解析等底层开发中。例如:

// 定义 UART 的 SR 寄存器 struct UART1_Register_SR { int PE : 1; // 奇偶校验错误 int FE : 1; // 帧错误 int NF : 1; // 噪声标识 int OR : 1; // 溢出错误 int IDLE : 1; // 空闲 int RXNE : 1; // 接收寄存器非空 int TC : 1; // 传输完成 int TXE : 1; // 发送寄存器非空 };

这里定义了一个结构体类型用于表示 UART(通用异步收发器)的 SR(状态) 寄存器。

该结构体包含 8 个字段,每个字段都只有 1 位;不考虑内存对齐的前提下,该结构体的大小是 1 字节。

创建于 2025/7/24

更新于 2025/7/24